package datatypes.kdTree;

import java.util.HashSet;
import java.util.Set;

import datatypes.Edge;
import datatypes.Vertex;

/**
 * A Range is a collection of minimum/maximum value pairs, one each for each axis.
 * 
 * @author nils
 *
 */
public class Range implements Cloneable {
//	double[][] range;
    double minX, maxX, minY, maxY;
    int numberOfAxes;
	
	/**
	 * Constructor for a new Range of zero-values; that is, the only value
	 * in this newly constructed Range is the origin of the coordinate system.
	 * See {@link #setMin(double, int)} and {@link #setMax(double, int)} for
	 * methods to change the range values.
	 * @param numberOfAxes
	 */
	public Range(int numberOfAxes) {
            this.numberOfAxes = numberOfAxes;
//            this.range = new double[2][numberOfAxes];
	}
	
//	/**
//	 * Constructor for a three-dimensional Range with the given range values.
//	 * @param minX
//	 * @param maxX
//	 * @param minY
//	 * @param maxY
//	 * @param minZ
//	 * @param maxZ
//	 */
//	public Range(	double minX, double maxX,
//					double minY, double maxY,
//					double minZ, double maxZ) {
//		this.range = new double[2][3];
//		this.range[0][0] = minX;
//		this.range[0][1] = minY;
//		this.range[0][2] = minZ;
//		this.range[1][0] = maxX;
//		this.range[1][1] = maxY;
//		this.range[1][2] = maxZ;
//		this.numberOfAxes = 3;
//	}
	
	/**
	 * Constructor for a two-dimensional Range with the given range values.
	 * @param minX
	 * @param maxX
	 * @param minY
	 * @param maxY
	 */
	public Range(	double minX, double maxX,
					double minY, double maxY) {
//		this.range = new double[2][2];
//		this.range[0][0] = minX;
//		this.range[0][1] = minY;
//		this.range[1][0] = maxX;
//		this.range[1][1] = maxY;
            this.minX = minX;
            this.maxX = maxX;
            this.minY = minY;
            this.maxY = maxY;
		this.numberOfAxes = 2;
	}
	
	public int getNumberOfAxes() {
//		return range[0].length;
        return numberOfAxes;
	}
	
//	/**
//	 * Get-accessor to the minimum value at the given axis.
//	 * @param axis
//	 * @return
//	 */
//	public double getMinWithSafetyCheck(int axis) {
//		if (axis >= numberOfAxes) {
//			System.err.println("Range error: Tried to access axis " + axis + ". Range has" + this.getNumberOfAxes() + " axes only.");
//			return 0;
//		}
//		return range[0][axis];
//	}

//        /**
//	 * Get-accessor to the minimum value at the given axis.
//         * Make sure that the axis actually exists!
//	 * @param axis
//	 * @return
//	 */
//	public double getMin(int axis) {
//		return range[0][axis];
//	}
	
	/**
	 * Set-accessor to the minimum value at the given axis.
	 * @param axis
	 * @return
	 */
	public void setMin(double value, int axis) {
		if (axis >= numberOfAxes) {
			System.err.println("Range error: Tried to access axis " + axis + ". Range has" + this.getNumberOfAxes() + " axes only.");
			return;
		}
            if (axis == 0) {
                minX = value;
            } else {
                minY = value;
            }
	}
	
//	/**
//	 * Get-accessor to the maximum value at the given axis.
//	 * @param axis
//	 * @return
//	 */
//	public double getMaxWithSafetyCheck(int axis) {
//		if (axis >= numberOfAxes) {
//			System.err.println("Range error: Tried to access axis " + axis + ". Range has" + this.getNumberOfAxes() + " axes only.");
//			return 0;
//		}
//		return range[1][axis];
//	}

//        /**
//	 * Get-accessor to the maximum value at the given axis.
//         * Make sure that the axis actually exists!
//	 * @param axis
//	 * @return
//	 */
//	public double getMax(int axis) {
//		return range[1][axis];
//	}
	
	/**
	 * Set-accessor to the maximum value at the given axis.
	 * @param axis
	 * @return
	 */
	public void setMax(double value, int axis) {
		if (axis >= numberOfAxes) {
			System.err.println("Range error: Tried to access axis " + axis + ". Range has" + this.getNumberOfAxes() + " axes only.");
			return;
		}
            if (axis == 0) {
                maxX = value;
            } else {
                maxY = value;
            }
	}
	
	/**
	 * Checks whether this range fully contains another range.
	 * Nota bene: If the dimension of the ranges does not match,
	 * the excess axes of the higher-dimensional range will be ignored. 
	 * 
	 * @param other
	 * @return
	 */
	public boolean contains(Range other) {
                if (this.minX > other.minX) {return false;}
                if (this.maxX < other.maxX) {return false;}
                if (this.minY > other.minY) {return false;}
                if (this.maxY < other.maxY) {return false;}
		return true;
	}
	
	/**
	 * Checks whether this range fully contains a vertex.
	 * Nota bene: If the dimension of the range and the vertex do not match,
	 * the excess axes of the higher-dimensional item will be ignored. 
	 * 
	 * @param vertex
	 * @return
	 */
	public boolean contains(Vertex vertex) {
            double vX = vertex.getX();
            if (this.minX > vX) {return false; }
            if (this.maxX < vX) {return false; }
            double vY = vertex.getY();
            if (this.minY > vY) {return false; }
            if (this.maxY < vY) {return false; }
            return true;
	}
	
	
//	/**
//	 * Checks whether this range intersects another range.
//	 * Nota bene: If the dimension of the ranges does not match,
//	 * the excess axes of the higher-dimensional range will be ignored.
//	 *
//	 * @param other
//	 * @return
//	 */
//	public boolean intersectsWithUnevenNumberOfAxes(Range other) {
//		int minNumberOfAxes = Math.min(this.numberOfAxes, other.getNumberOfAxes());
//		for (int i = 0; i < minNumberOfAxes; i++) {
//			if (this.getMin(i) > other.getMax(i)) { return false; }
//			if (this.getMax(i) < other.getMin(i)) { return false; }
//		}
//		return true;
//	}

        /**
	 * Checks whether this range intersects another range.
	 * Nota bene: The number of axes has to match!
	 *
	 * @param other
	 * @return
	 */
	public boolean intersects(Range other) {
                if (this.minX > other.maxX) { return false; }
                if (this.minY > other.maxY) { return false; }
                if (this.maxX < other.minX) { return false; }
                if (this.maxY < other.minY) { return false; }
		return true;
	}

        /**
	 * Checks whether this range intersects another range.
	 * Nota bene: The number of axes has to match!
	 *
	 * @param other
	 * @return
	 */
	public boolean intersects2d(Range other) {
		if (this.minX > other.maxX) { return false; }
                if (this.minY > other.maxY) { return false; }
                if (this.maxX < other.minX) { return false; }
                if (this.maxY < other.minY) { return false; }
		return true;
	}
	
	public Set<Vertex> getVertices() {
		if (this.getNumberOfAxes() != 2) {
			// since Vertices in this project only support 2 dimensions:
			throw new UnsupportedOperationException();
		}
		Set<Vertex> vertices = new HashSet<Vertex>();
		vertices.add(new Vertex(minX, minY));
		vertices.add(new Vertex(minX, maxY));
		vertices.add(new Vertex(maxX, maxY));
		vertices.add(new Vertex(maxX, minY));
		return vertices;
	}
	
	public Set<Edge> getEdges() {
		if (this.getNumberOfAxes() != 2) {
			// since Vertices in this project only support 2 dimensions:
			throw new UnsupportedOperationException();
		}
		Vertex v1 = new Vertex(minX, minY);
		Vertex v2 = new Vertex(minX, maxY);
		Vertex v3 = new Vertex(maxX, maxY);
		Vertex v4 = new Vertex(maxX, minY);
		
		Set<Edge> edges = new HashSet<Edge>();
		edges.add(new Edge(v1, v2));
		edges.add(new Edge(v2, v3));
		edges.add(new Edge(v3, v4));
		edges.add(new Edge(v4, v1));
		return edges;
	}
	
	/**
	 * Deep copy of this range.
	 */
	public Range clone() {
			Range clone;
			try {
				clone = (Range) super.clone();
				clone.numberOfAxes = this.getNumberOfAxes();
//				clone.range = new double[2][this.getNumberOfAxes()];
//				clone.range[0] = this.range[0].clone();
//				clone.range[1] = this.range[1].clone();
				return clone;
			} catch (CloneNotSupportedException e) {
				e.printStackTrace();
			}
			
			return null;
	}
	
	/**
	 * Returns true if all values in both ranges are equal AND both ranges have the same dimension.
	 * @param other
	 * @return
	 */
	public boolean equals(Range other) {
            if (this.getNumberOfAxes() == other.getNumberOfAxes()) {return false;}
                        if (this.minX != other.minX) { return false; }
                        if (this.maxX != other.maxX) { return false; }
                        if (this.minY != other.minY) { return false; }
                        if (this.maxY != other.maxY) { return false; }
            return true;
	}
	
	public boolean equals(Object o) {
		if (! (o instanceof Range)) {
			return false;
		}
		return equals((Range)o);
	}
	
	@Override
	public int hashCode() {
		return (int) minX;
	}
	
//	@Override
//	public String toString() {
//		StringBuffer buffer = new StringBuffer();
//		buffer.append("Axis | min, max \n");
//		for (int i = 0; i < this.getNumberOfAxes(); i++) {
//			buffer.append(i + "    | " + this.getMin(i) + ", " + this.getMax(i) + "\n");
//		}
//		return buffer.toString();
//	}
}
